/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.mvc.method.annotation;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;

/**
 * Resolves method arguments annotated with {@code @RequestBody} and handles return
 * values from methods annotated with {@code @ResponseBody} by reading and writing
 * to the body of the request or response with an {@link HttpMessageConverter}.
 *
 * <p>An {@code @RequestBody} method argument is also validated if it is annotated
 * with {@code @javax.validation.Valid}. In case of validation failure,
 * {@link MethodArgumentNotValidException} is raised and results in an HTTP 400
 * response status code if {@link DefaultHandlerExceptionResolver} is configured.
 *
 * @author Arjen Poutsma
 * @author Rossen Stoyanchev
 * @author Juergen Hoeller
 * @since 3.1
 */
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

	/**
	 * Basic constructor with converters only. Suitable for resolving
	 * {@code @RequestBody}. For handling {@code @ResponseBody} consider also
	 * providing a {@code ContentNegotiationManager}.
	 */
	public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
		super(converters);
	}

	/**
	 * Basic constructor with converters and {@code ContentNegotiationManager}.
	 * Suitable for resolving {@code @RequestBody} and handling
	 * {@code @ResponseBody} without {@code Request~} or
	 * {@code ResponseBodyAdvice}.
	 */
	public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, ContentNegotiationManager manager) {
		super(converters, manager);
	}

	/**
	 * Complete constructor for resolving {@code @RequestBody} method arguments.
	 * For handling {@code @ResponseBody} consider also providing a
	 * {@code ContentNegotiationManager}.
	 * @since 4.2
	 */
	public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, List<Object> requestResponseBodyAdvice) {
		super(converters, null, requestResponseBodyAdvice);
	}

	/**
	 * Complete constructor for resolving {@code @RequestBody} and handling
	 * {@code @ResponseBody}.
	 */
	public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, ContentNegotiationManager manager, List<Object> requestResponseBodyAdvice) {
		super(converters, manager, requestResponseBodyAdvice);
	}


	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		// 判断参数是否标记了@RequestBody注解即可
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		// 返回值所在类或者返回值对应的方法上是否有ResponseBody注解
		// 这里额外判断返回值对应的方法所在类上是否标记有@ResponseBody，是为了实现在处理器类上声明的@ResponseBody对该处理器内所有处理器方法均生效的目的，多用于@RestController这类声明
		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
				returnType.hasMethodAnnotation(ResponseBody.class));
	}

	/**
	 * Throws MethodArgumentNotValidException if validation fails.
	 * @throws HttpMessageNotReadableException if {@link RequestBody#required()}
	 * is {@code true} and there is no body content or if there is no suitable
	 * converter to read the content with.
	 */
	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		// 如果参数类型是Optional，获取泛型类型
		parameter = parameter.nestedIfOptional();
		// 通过消息转换器读取消息，传入请求、参数与参数中的泛型类型
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		// 获取参数对应的变量名，一般取简单类型名，首字母小写
		String name = Conventions.getVariableNameForParameter(parameter);
		// 创建一个WebDataBinder，传入已经转换后的结果与变量名
		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
		if (arg != null) {
			// 则通过校验器Validator对参数结果执行校验，只有参数上标记了@Valid或@Validate注解，才执行参数值校验逻辑
			validateIfApplicable(binder, parameter);
			// 如果参数绑定的校验结果存在错误，则说明有不符合校验规则的属性，同时如果该参数的下一个参数不是用来接收绑定错误的(通过参数类型判断)，则直接抛出异常
			if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
				throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
			}
		}
		// 无须抛出绑定过异常时，把绑定结果添加到ModelAndView容器的org.springframework.validation.BindingResult + name属性中
		mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

		// 对解析的参数结果执行适配，适配逻辑为判断参数类型是否是Optional，如果是，则把结果封装为Optional，否则直接返回原值
		return adaptArgumentIfNecessary(arg, parameter);
	}

	@Override
	protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
		// 获取原始请求
		HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
		// 通过原始请求构造一个输入消息，用于提供给消息转换器使用
		ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);

		// 通过消息转换器来读取消息，消息转换器需要的类型是ServletServerHttpRequest，所以该方法只是做了个类型转换
		Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
		// 当解析结果为空时，检查是否是必需参数，如果@RequestBody的required为false表示不必需，required默认为true；或者参数类型是Optional，也表示非必需
		if (arg == null) {
			if (checkRequired(parameter)) {
				// 为必需参数时，而且其值为null，则抛出异常
				throw new HttpMessageNotReadableException("Required request body is missing: " + parameter.getMethod().toGenericString());
			}
		}
		// 返回解析结果，即为最终绑定的参数
		return arg;
	}

	protected boolean checkRequired(MethodParameter parameter) {
		return (parameter.getParameterAnnotation(RequestBody.class).required() && !parameter.isOptional());
	}

	@Override
	public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
		// 通过同期标记请求已被处理，后续无须再使用ModelAndView策略处理视图，直接结束后续处理。在RequestMappingHandlerAdapter最后的getModelAndView逻辑中判断该标记，如果为true直接返回null作为ModelAndView
		mavContainer.setRequestHandled(true);
		// 准备输入消息
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		// 准备输出消息
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// 使用消息转换器把返回值写入消息
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

}
